\chapter{Combinación de Antirretrovirales}

\section{Introducción}
En este capítulo se describe como surge la necesidad de combinar antirretrovirales. Ademas se introducen las distinta primitivas combinatorias provistas por el SDK y finalmente se explica como se asocia el concepto de fallo virológico con la modificación de las primitivas de combinación.
 
\section{¿Por qué usar combinaciones de antirretrovirales?}
Los antirretrovirales individualmente no suprimen la infección por VIH a largo plazo, por lo cual actualmente se usan combinaciones de estos.
Las combinaciones de antirretrovirales actúan incrementando el número de obstáculos para la mutación viral, manteniendo bajo el número de copias virales.
Las combinaciones de antirretrovirales pueden ser de sinergismo positivo o negativo. Esto limita el número de combinaciones disponibles. Por ejemplo, la combinación de $ddI$ y $AZT$ es de sinergismo negativo, ya que tomados juntos, cada fármaco inhibe la acción del otro.
Otros factores que limitan las combinaciones son la aparición de efectos colaterales severos. La necesidad de un horario de administración complicado, dificulta la adherencia apropiada al tratamiento. La infección por el virus de hepatitis B (VHB) puede dificultar que las personas con VIH tomen medicación antirretroviral (ARV). Debido a que el VHB puede ocasionar graves daños hepáticos si no se trata ya que muchos ARVs son metabolizados por el hígado.
	
\section{Funcionalidad}
Siguiendo con la idea de un sistema altamente parametrizable. Se optó por proporcionar las primitivas combinatorias al sistema, a través del $Plugin$. Además, a diferencia de las políticas de generación (cap. 7) que se establecen al comienzo de la ejecución, la primitiva combinatoria debía poder cambiar en cada nodo de la terapia (RF13). Así, en caso de un fallo virológico la terapia podía tomar el rumbo que el usuario determine a través de un cambio en la primitiva de combinación.\\

Una primitiva combinatoria representa una forma de combinar antirretrovirales. Es decir, debe generar subconjuntos de un conjunto de antirretrovirales. Además, al igual que en todo el sistema se usa el patrón $Observer$, las primitivas deben notificar al $observador\ de\ combinaciones$ cada nueva combinación. Finalmente, al terminar debe retornar un objeto $Status$ indicando si hubo o no fallo virológico.\\
Desde el punto de vista del sistema, el $observador\ de\ combinaciones$ recibe una combinación, genera las mutantes resultantes de aplicar esa combinación y las entrega al siguiente observador.

Todos las primitivas deben implementan la interfaz $GenerationPolicy$ la cual consta de dos métodos importantes:

\begin{itemize}
\item $void\ set\_observer(observer)$
Establece el observador de combinaciones, el cual va a recibir las combinaciones a medida que se generan.
\item $void\ combine(antivirals, status)$
Este método realiza las combinaciones a partir del conjunto de antirretrovirales. Ademas, toma como argumento un objeto $Status$ para establecer el estado luego de combinar.
\end{itemize}

\section{Lista de primitivas combinatorias}
Dada la complejidad de generar algoritmos combinatorios, se proveyó junto con el sistema de un conjunto acotado de algoritmos (SDK). Este conjunto no implementa algoritmos complicados, pesados y difíciles de mantener. En su lugar se opto por un grupo de algoritmos simples y ensamblables, de esta forma el usuario puede desarrollar primitivas tan complejos como desee usando unidades simples y fáciles de manipular. \\

Las primitivas se pueden dividir en dos clases, simples y compuestas. Las primitivas simples son algoritmos combinadores propiamente dichos. En cambio, las compuestas permiten combinar a las simples, pero no producen combinaciones por si solas.

A continuación se listan y explican las primitivas simples provistas en el SDK.\\


\subsection{Combinador vacío (EmptyCombinatory)}
Esta primitiva cumple la función de $skip$, no retorna ninguna combinación y el $Status$ no indica fallo virológico.

\subsection{Combinador Newtoniano (CombinatoryNewton)}
En este caso, se implementa el clásico algoritmo que retorna todos los subconjuntos de tamaño $K$ de un conjunto de tamaño $N$. El $Status$ retorna fallo virológico ($Fail$) en caso que el conjunto de antirretrovirales sea vacío ($N$ = 0) o si $K > N$\\


\begin{lstlisting}[language=C++, numbers=left, numberstyle=\tiny, tabsize=4,fontadjust=true,frame=trBL,basicstyle=\tt, tabsize=4]
void choose(antivirals, k)
{
    combination = empty_combination(k)
    comb_builder(combination, k, antivirals)
}
\end{lstlisting}

choose toma como argumento el conjunto de antivirales de tamaño $N$ y el tamaño de los subconjuntos $K$. Ademas, internamente llama al método recursivo $comb\_builder$ con una combinación vacía.\\

\begin{flushleft}
\begin{minipage}{\linewidth}
\begin{lstlisting}[language=C++, numbers=left, numberstyle=\tiny, tabsize=4,fontadjust=true,frame=trBL,basicstyle=\tt, tabsize=4]
void comb_builder(combination, pos, antivirals)
{
    if (pos == 0)
    {
        notify(combination)
    }
    else
    {
        for all antiviral in antivirals do
        {
            combination[pos-1] = antiviral
            comb_builder(combination,pos-1,antivirals-{antiviral})
        }
    }
}
\end{lstlisting}
\end{minipage}
\end{flushleft}

Esta sub rutina toma un antirretroviral del conjunto, lo introduce en la posición $pos$ de la combinación y se llama recursivamente con esta combinación, la posición anterior (libre) y el conjunto de antirretrovirales menos el antirretroviral que ya esta contenido en la combinación. Así, sucesivamente hasta llenar la combinación con k elementos. Cuando llega a la posición 0 (caso base) notifica al observador la nueva combinación.

%---------------------------------------------------------
\subsection{Listado Simple (ListCombinatory)}
Este algoritmo retorna uno a uno los antivirales del conjunto que se le da como argumento. Es decir, retorna combinaciones de un único antirretroviral.
El $Status$ retorna $Fail$ cuando el conjunto de antirretrovirales de entrada es vacío.  

\begin{lstlisting}[language=C++, numbers=left, numberstyle=\tiny, tabsize=4,fontadjust=true,frame=trBL,basicstyle=\tt, tabsize=4]
void combine(antivirals, status)
{
    if (antivirals != empty)
    {
    	for all antiviral in antivirals do
        {
            update(antiviral)
        }
        status = Success
    }
    else
    {
        status = Fail
    }
}
\end{lstlisting}


El funcionamiento es simple, verifica que el conjunto de antirretrovirales no sea vacío. Luego por cada antirretroviral del conjunto llama a la sub rutina $update$ del observador para notificar la nueva combinación. \\


A continuación se explican las primitivas compuestas provistas por el SDK.
%---------------------------------------------------------
\subsection{Combinador Secuencial (SeqCombinatorialGroup)}
Este algoritmo funciona como nexo entre diferentes algoritmos. Básicamente, toma un conjunto de combinadores y los hace combinar en secuencia, uno detrás de otro. Podría pensarse como una cola (FIFO) de combinadores, el primero en ser introducido es el primero en combinar.
Esta primitiva provee el método $add\_policy(combine\_alg, antivirals)$, donde $combine\_alg$ es el algoritmo combinador y $antivirals$ es el conjunto de antirretrovirales con los cuales se le permite combinar.\\


\begin{figure}
\centering
\includegraphics[scale=0.3]{pictures/Comb1.jpg}	  	
\caption{Combinador Secuencial.} 
\end{figure}

\begin{flushleft}
\begin{minipage}{\linewidth}
\begin{lstlisting}[language=C++, numbers=left, numberstyle=\tiny, tabsize=4,fontadjust=true,frame=trBL,basicstyle=\tt, tabsize=4]
void combine(available_av, status)
{
	status = Fail
	aux_status = Fail
	
    for all combinator in set_of_combinators do
    {
        intersec = set_intersection(available_av,get_av(combinator))

        combine(intersec, aux_status)
        if (aux_status == Success)
        {
        	status = aux_status
        } 
    }
}
\end{lstlisting}
\end{minipage}
\end{flushleft}

El algoritmo toma un $combinador$ del conjunto, realiza la intersección entre los antirretrovirales disponibles y aquellos que acepta el combinador. Luego, combina la intersección de antirretrovirales y guarda el status en $aux\_status$. Si el $aux\_status$ es $Success$ pisamos en valor de $status$, en caso contrario se deja $Fail$. De esta forma, si todos los algoritmos combinatorios fallan, se considera que el secuencial fallo.

%---------------------------------------------------------
\subsection{Combinador Paralelo (ParallelCombinatory)}
Este algoritmo toma un grupo de primitivas combinatorias y las hace correr en "paralelo". De manera que, cada combinación del primero se une con cada combinación del siguiente y así sucesivamente hasta que la última combinación es entrega al observador.

\begin{figure}
\centering
\includegraphics[scale=0.3]{pictures/Comb2.jpg}	  	
\caption{Combinador Paralelo.} 
\end{figure}

Esta primitiva tiene internamente una lista de $N$ observadores donde los primeros $N-1$ se denominan observadores auxiliares ($AuxObserver$) y el último observador final ($FinalObserver$). Hay exactamente un observador auxiliar por cada primitiva combinatoria contenida en la primitiva paralela, la función de estos observadores es actuar como nexo entre primitivas y unir las combinaciones intermedias. El observador final se encarga de trasmitir la combinación resultante al observador de la primitiva paralela.\\

\begin{flushleft}
\begin{minipage}{\linewidth}
\begin{lstlisting}[language=C++, numbers=left, numberstyle=\tiny, tabsize=4,fontadjust=true,frame=trBL,basicstyle=\tt, tabsize=4]
void add_policy(policy, reference_av, is_mandatory)
{
    aux_observer = new AuxObserver(policy,reference_av,is_mandatory)
    enqueue(aux_observer)
}
\end{lstlisting}
\end{minipage}
\end{flushleft}

$add\_policy$ se encarga de incorporar primitivas combinatorias al combinador paralelo. Toma como argumento la primitiva combinatoria ($policy$), el conjunto de antirretrovirales con los que se le permite combinar a la política ($reference\_av$) y un flag que indica si la primitiva es obligatoria o no ($is\_mandatory$). En caso de ser obligatoria, si no genera combinaciones se considera fallo virológico. Si no es obligatoria, y no genera combinaciones es ignorada y se continua con la siguiente en la cola.\\
 

\begin{lstlisting}[language=C++, numbers=left, numberstyle=\tiny, tabsize=4,fontadjust=true,frame=trBL,basicstyle=\tt, tabsize=4]
void combine(available_av, status)
{
    combine_obs(first_obs, empty_comb, available_av,status)           
}
\end{lstlisting}

El método $combine$ de la primitiva paralela implementa la interfaz $CombinatoryPolicy$. Su única función es llamar al método $combine\_obs$ del primer observador en la lista, con una combinación vacía, los antirretrovirales disponibles y el status.\\

\begin{flushleft}
\begin{minipage}{\linewidth}
\begin{lstlisting}[language=C++, numbers=left, numberstyle=\tiny, tabsize=4,fontadjust=true,frame=trBL,basicstyle=\tt, tabsize=4]
void combine_obs(observer, before_comb, available_av, status)
{
    intersec = set_intersection(reference_av, available_av)

    combine(intersec, status)
    
    if (status == Fail)
    {
        if (!is_mandatory)
        {
            combine_obs(next_obs, before_comb, available_av,status)
        }
        else
        {
            notify_fail(status)
        }
    }
}
\end{lstlisting}
\end{minipage}
\end{flushleft}


Este método ($combine\_obs$) toma el observador sobre el que va a combinar, la combinación del combinador anterior, los antirretrovirales disponibles para combinar y el status.
Primero, calcula la intersección entre el conjunto de antirretrovirales de referencia y el conjunto disponible para combinar ($intersec$). Luego, ejecuta la primitiva combinatoria con $intersec$. Si el status resultante de la combinación es $Fail$ hay dos opciones:
\begin{itemize}
\item la primitiva no es obligatoria, en cuyo caso se llama al combinador del siguiente observador auxiliar $next\_obs$ con la combinación anterior $before\_comb$.
 
\item la primitiva es obligatoria, en este caso se notifica un fallo virológico. \\
 
\end{itemize}

\begin{lstlisting}[language=C++, numbers=left, numberstyle=\tiny, tabsize=4,fontadjust=true,frame=trBL,basicstyle=\tt, tabsize=4]
void update_obs(combination)
{
    union = set_union(combination, before_combination)
    combine_obs(next_observer, union, available_av, status)
}
\end{lstlisting}

Este método es llamado cada vez que la primitiva, asociada a este observador auxiliar, genera una combinación ($combination$). Básicamente, se une la primitiva actual con la anterior ($union$) y se llama al combinador del siquiente observador. \\


\begin{lstlisting}[language=C++, numbers=left, numberstyle=\tiny, tabsize=4,fontadjust=true,frame=trBL,basicstyle=\tt, tabsize=4]
void combine_obs(observer, combination, available_av,status)
{
    update(parallel_observer, combination)
}
\end{lstlisting}

Esta versión de $combine\_obs$ pertenece al observador final y implementa la misma interfaz. La función de este método es enviar la combinación resultante de la unión de todas las combinaciones al observador de la primitiva paralela.\\


\textbf{Ejemplo} de uso de la primitiva paralela:\\

\begin{lstlisting}[language=C++, numbers=left, numberstyle=\tiny, tabsize=4,fontadjust=true,frame=trBL,basicstyle=\tt, tabsize=4]

subset_1 = filter(available_antivirals, cPI)
subset_2 = filter(available_antivirals, cNRTI)
subset_3 = filter(available_antivirals, cNNRTI)

list_comb_1 = new ListCombinatory("list cPi")
list_comb_2 = new ListCombinatory("list cNRTI")
list_comb_3 = new ListCombinatory("list cNNRTI")
    
example_parallel = new ParallelCombinatory("parallel")
example_parallel.add_policy(list_comb_1, subset_1, true)
example_parallel.add_policy(list_comb_2, subset_2, true)
example_parallel.add_policy(list_comb_3, subset_3, false)

example_parallel.combine(apply_antivirals, status)
\end{lstlisting}


En el ejemplo se hace uso del método $filter$, una de las herramientas provistas por el SDK para la construcción de plugins. $filter$ genera un subconjunto ($subset\_N$) de un conjunto mayor ($available\_antivirals$) con todos los elementos que cumplen cierto parámetro ($cPI$, $cNRTI$ y $cNNRTI$). Luego se crean tres primitivas de listado simple ($list\_comb_N$), una para cada subconjunto de antirretrovirales. A continuación, se crea la primitiva paralela ($example\_parallel$) y se le incorporan las primitivas secuenciales a través de $add\_policy$. Finalmente, obtenemos una primitiva que realiza combinaciones de tamaño tres de los cuales uno es de tipo $cPI$, otro de tipo $cNRTI$ y el último de tipo $cNNRTI$.\\


%---------------------------------------------------------
\section{Cambio de la primitiva combinatoria en caso de Fallo Virológico}
Como respuesta a los requerimientos RF5.7 y RF13 se hicieron modificaciones en el sistema y en la interfaz de plugin para que se pudiera cambiar la primitiva combinatoria en cada nodo de una terapia. Esto es posible gracias al método $get\_new\_combination\_policy(status)$ presente como interfaz en cada nodo. Así, si se recibe un status $Fail$ (fallo virológico) por parte de una primitiva, el sistema pregunta al plugin por una nueva. En caso que el plugin entregue una nueva primitiva para ese nodo el sistema hace combinar la nueva primitiva. En caso contrario se da por finalizada la terapia.
